﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml;
using System.Net;
using System.IO;
using System.Configuration;

namespace Bloggregator
{
    [DefaultProperty("Text")]
    [ParseChildren(true, "Text")]
    [ToolboxData("<{0}:BloggregatorControl runat=server></{0}:BloggregatorControl>")]
    public class BloggregatorControl : WebControl
    {
        //default timeout for each site
        int DEFAULT_TIMEOUT_OF_EACH_SITE = 30; //in seconds
        int DEFAULT_HISTROY              = 60; //in days

        class FeedReference
        {
            public string feedUrl;
            public string[] categories;
            public string author;
        }

        string channelDiscription;
        FeedReference[] feedReferences;
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            string configFile = ConfigurationSettings.AppSettings["AggregatorConfig"];
            if(configFile != null)
                feedReferences = GetFeedList(configFile);
        }

        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public string Text
        {
            get
            {
                if (feedReferences == null)
                    return "<feed>\n</feed>";

                XmlDocument doc = new XmlDocument();
                doc.LoadXml("<rss version='2.0'></rss>");

                GetMatchingArticleNodes(feedReferences, ref doc);

                return doc.OuterXml;
            }
            set
            {
                ViewState["Text"] = value;
            }
        }

        [
         Bindable(true),
         Category("Behavior"),
         DefaultValue(""),
         Description("Time out when fetch feed from each site")
         ]
        public virtual int TimeOut
        {
            get
            {
                if (ViewState["TimeOut"] != null)
                {
                    return Convert.ToInt32(ViewState["TimeOut"]);
                }
                return DEFAULT_TIMEOUT_OF_EACH_SITE;
            }
            set
            {
                ViewState["TimeOut"] = value;
            }
        }

        [
         Bindable(true),
         Category("Behavior"),
         DefaultValue(""),
         Description("Days back when fetch feed from each site")
         ]
        public virtual int History
        {
            get
            {
                if (ViewState["History"] != null)
                {
                    return Convert.ToInt32(ViewState["History"]);
                }
                return DEFAULT_HISTROY;
            }
            set
            {
                ViewState["History"] = value;
            }
        }

        //no rendering -- just return plain text
        protected override void RenderContents(HtmlTextWriter output)
        {
            output.WriteEncodedText(Text);
        }

        /// <summary>
        /// Retrieves the list of blog feeds from the indicated config file
        /// </summary>
        /// <param name="configFile"></param>
        /// <returns></returns>
        FeedReference[] GetFeedList(string configFile)
        {
            UInt32 feedIndex = 0;
            UInt32 categoryIndex = 0;

            // Create the Xml Document
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(configFile);

            channelDiscription = xmlDocument.SelectSingleNode("AggregatorConfig/Channel/Description").InnerText;

            // Get the Feed nodes
            XmlNodeList feedNodes =
                xmlDocument.SelectNodes("AggregatorConfig/Feed");

            FeedReference[] feedReferences =
                new FeedReference[feedNodes.Count];

            // Iterate over the Feed nodes
            foreach (XmlNode feedNode in feedNodes)
            {
                // Get the URL
                XmlNode feedUrl = feedNode.SelectSingleNode("URL");

                // Create a FeedReference to add to the list to be returned 
                // to the caller
                FeedReference feedReference = new FeedReference();
                feedReference.feedUrl = feedUrl.InnerText;

                // Get the author
                XmlNode author = feedNode.SelectSingleNode("Author");
                feedReference.author = author.InnerText;

                // Get the Category nodes for this Feed
                XmlNodeList categoryNodes = feedNode.SelectNodes("Category");

                feedReference.categories = new string[categoryNodes.Count];

                // Iterate over the Category nodes
                categoryIndex = 0;
                foreach (XmlNode categoryNode in categoryNodes)
                {
                    feedReference.categories[categoryIndex] =
                        categoryNode.InnerText;
                }

                feedReferences[feedIndex] = feedReference;
                feedIndex++;
            }

            return feedReferences;
        }

        /// <summary>
        /// Find the articles in the given feed for the requested categories
        /// </summary>
        /// <param name="feed"></param>
        /// <param name="articles"></param>
        void _GetMatchingArticleNodesPerFeed(
            FeedReference feedReference,
            ref XmlDocument document)
        {
            // Create a web request for the URL
            WebRequest webRequest = WebRequest.Create(feedReference.feedUrl);
            webRequest.Timeout = TimeOut * 1000;

            // Get the response from the WebRequest
            WebResponse webResponse = webRequest.GetResponse();

            // Get the response's stream
            Stream rssStream = webResponse.GetResponseStream();

            // Create the Xml Document
            XmlDocument xmlDocument = new XmlDocument();

            // Load the stream into the XmlDocument object.
            xmlDocument.Load(rssStream);

            //Create a XmlNamespaceManager for our namespace.
            XmlNamespaceManager manager =
                new XmlNamespaceManager(xmlDocument.NameTable);

            //Add the RSS namespace to the manager.
            manager.AddNamespace("rss", "http://purl.org/rss/1.0/");

            //should only have one channel in the list
            XmlNodeList channelNodes =
                xmlDocument.SelectNodes("/rss/channel", manager);

            XmlNode channelNode = channelNodes[0];

            string link = channelNode.SelectSingleNode("link").InnerText;
            string title = channelNode.SelectSingleNode("title").InnerText;

            XmlNode channel = document.DocumentElement.SelectSingleNode("channel");

            //Loop through the articles and extract
            // their data.
            foreach (XmlNode node in channelNode.ChildNodes)
            {
                bool foundMatch = false;

                //Get the article's categories
                XmlNodeList categoryNodes =
                    node.SelectNodes("category", manager);

                // Check for a category match
                foreach (XmlNode categoryNode in categoryNodes)
                {
                    for (
                        UInt32 categoryIndex = 0;
                        categoryIndex < feedReference.categories.Length;
                        categoryIndex++)
                    {
                        if (categoryNode.InnerText ==
                            feedReference.categories[categoryIndex])
                        {
                            foundMatch = true;
                            break;
                        }
                    }

                    if (true == foundMatch)
                        break;
                }

                if (true == foundMatch)
                {
                    // check for date
                    XmlNodeList article = node.SelectNodes("pubDate", manager);
                    DateTime dt = DateTime.Parse(article[0].InnerText);

                    System.TimeSpan diff = DateTime.Now.Subtract(dt);
                    if (diff.Days < History ||
                        diff.Days == History && diff.Hours + diff.Minutes + diff.Seconds == 0)
                    {
                        XmlNode newNode = document.ImportNode(node, true);

                        //add author
                        XmlNode author = document.CreateElement("author");
                        XmlNode authorName = document.CreateTextNode(feedReference.author);
                        author.AppendChild(authorName);

                        newNode.AppendChild(author);

                        //add source
                        XmlNode source = document.CreateElement("source");
                        XmlNode attrib = document.CreateAttribute("url");
                        attrib.Value = link;

                        XmlNode sourcetext = document.CreateTextNode(title);
                        source.Attributes.SetNamedItem(attrib);
                        source.AppendChild(sourcetext);
                        newNode.AppendChild(source);

                        channel.AppendChild(newNode);
                    }
                }
            }
        }

        /// <summary>
        /// Returns a list of XML nodes representing the RSS items (blog posts)
        /// that match the categories listed in the provided feeds.
        /// </summary>
        /// <param name="feedReferences"></param>
        /// <returns></returns>
        void GetMatchingArticleNodes(
            FeedReference[] feedReferences,
            ref XmlDocument document)
        {
            XmlNode channel = document.CreateElement("channel");

            //description
            XmlNode description = document.CreateElement("description");
            XmlNode descriptionText = document.CreateTextNode(channelDiscription);
            description.AppendChild(descriptionText);
            channel.AppendChild(description);

            //title
            XmlNode title = document.CreateElement("title");
            XmlNode titleText = document.CreateTextNode("Consolidated RSS Feed");
            title.AppendChild(titleText);
            channel.AppendChild(title);

            //link
            XmlNode link = document.CreateElement("link");
            XmlNode linkText = document.CreateTextNode("http://bloggregator.codeplex.com");
            link.AppendChild(linkText);
            channel.AppendChild(link);

            document.DocumentElement.AppendChild(channel);

            for (
                UInt32 feedReference = 0;
                feedReference < feedReferences.Length;
                feedReference++)
            {
                GetMatchingArticleNodes(
                    feedReferences[feedReference],
                    ref document);
            }
        }

        /// <summary>
        /// Returns a list of XML nodes representing the RSS items (blog posts)
        /// that match the categories listed in the provided feed.
        /// </summary>
        /// <param name="feedReferences"></param>
        /// <returns></returns>
        void GetMatchingArticleNodes(
            FeedReference feedReference,
            ref XmlDocument document)
        {
            try
            {
                _GetMatchingArticleNodesPerFeed(
                    feedReference,
                    ref document);
            }
            catch (TimeoutException)
            {
                //continue to next site
                //log to somewhere
            }
        }
    }
}
